#include "AvmUpdateItemPanel.h"
#include <wx/xrc/xmlres.h>
#include <wx/textfile.h>
#include "flash_tool.h"
#include <Utils.h>

MountPointsMap AvmUpdateItemPanel::TmpMountPoints = { {"${rootfs}", "/tmp/rootfs"}, {"${opt}", "/tmp/opt"}};
const wxEventType AvmUpdateItemPanel::Evt_DeleteItem = wxNewEventType();

BEGIN_EVENT_TABLE(AvmUpdateItemPanel, wxScrolledWindow)
    EVT_COMBOBOX(XRCID("cmb_item_type"), AvmUpdateItemPanel::OnUpdateItemTypeChange)
    EVT_BUTTON(XRCID("btn_browse_src"), AvmUpdateItemPanel::OnBrowseSourceFile)
    EVT_BUTTON(XRCID("btn_extract"), AvmUpdateItemPanel::OnBtnExtract)
    EVT_BUTTON(XRCID("btn_delete"), AvmUpdateItemPanel::OnBtnDelete)
END_EVENT_TABLE()
AvmUpdateItemPanel::AvmUpdateItemPanel(wxWindow* parent, bool new_add, const wxString& output)
//    : wxPanel(parent, wxID_ANY)
    : dst_file_sizer(NULL), emmc_pos_sizer(NULL), cmd_sizer(NULL)
    , ui_ok(false), is_new(new_add), auto_remove(true), output_bin(output)
    , source_offset(wxInvalidOffset), source_size(0)
{
    if(wxXmlResource::Get()->LoadPanel(this, parent, wxT("avm_update_item_panel")))
    {
        InitUI();
    }
}

AvmUpdateItemPanel::~AvmUpdateItemPanel()
{
    //dtor
}

void AvmUpdateItemPanel::InitUI()
{
    wxSizer* top_sizer = GetSizer();
    src_file_sizer = top_sizer->GetItemById(XRCID("src_file_sizeritem"), true);
    dst_file_sizer = top_sizer->GetItemById(XRCID("dst_file_sizeritem"));
    emmc_pos_sizer = top_sizer->GetItemById(XRCID("raw_offset_sizeritem"));
    cmd_sizer = top_sizer->GetItemById(XRCID("cmd_sizeritem"));
    extract_sizer = top_sizer->GetItemById(XRCID("extract_sizeritem"));
    process_sizer = top_sizer->GetItemById(XRCID("process_sizeritem"));

    cmb_type = XRCCTRL(*this, "cmb_item_type", wxComboBox);
    txt_src = XRCCTRL(*this, "txt_src_file", wxTextCtrl);
    btn_browse_src = XRCCTRL(*this, "btn_browse_src", wxButton);
    cmb_dst_part = XRCCTRL(*this, "cmb_mmc_part", wxComboBox);
    txt_dst = XRCCTRL(*this, "txt_dst_path", wxTextCtrl);
    cmb_emmc_pos = XRCCTRL(*this, "cmb_emmc_pos", wxComboBox);
    txt_cmdline = XRCCTRL(*this, "txt_cmdline", wxTextCtrl);
    btn_extract = XRCCTRL(*this, "btn_extract", wxButton);
    if(src_file_sizer && dst_file_sizer && emmc_pos_sizer && cmd_sizer &&  extract_sizer
        && process_sizer
        && cmb_type && txt_src && btn_browse_src && cmb_dst_part && txt_dst && txt_cmdline
        && cmb_emmc_pos && btn_extract)
    {
        ui_ok = true;
        process_sizer->Show(false);
    }
    if(cmb_type)
    {
        SelectUpdateItemType(cmb_type->GetSelection());
    }
}

void AvmUpdateItemPanel::OnUpdateItemTypeChange(wxCommandEvent& evt)
{
    int sel = evt.GetSelection();
    SelectUpdateItemType(sel);
    GetSizer()->Layout();
}

void AvmUpdateItemPanel::SelectUpdateItemType(int sel)
{
    if(!ui_ok)
        return;
    switch(sel)
    {
    case SECTION_COMMAND: //command
        src_file_sizer->Show(false);
        dst_file_sizer->Show(false);
        emmc_pos_sizer->Show(false);
        cmd_sizer->Show(true);
        extract_sizer->Show(false);
        cmb_type->SetSelection(SECTION_COMMAND);
        break;
    case SECTION_FS_FILE://file system
        src_file_sizer->Show(true);
        dst_file_sizer->Show(true);
        emmc_pos_sizer->Show(false);
        cmd_sizer->Show(false);
        extract_sizer->Show(!is_new);
        cmb_type->SetSelection(SECTION_FS_FILE);
        break;
    case SECTION_RAW_DATA://raw pos
        src_file_sizer->Show(true);
        dst_file_sizer->Show(false);
        emmc_pos_sizer->Show(true);
        cmd_sizer->Show(false);
        extract_sizer->Show(!is_new);
        cmb_type->SetSelection(SECTION_RAW_DATA);
        break;
    default:
        break;
    }
}

void AvmUpdateItemPanel::SetNewFlag(bool new_add)
{
    is_new = new_add;
    if(ui_ok)
    {
        long txt_style;
        cmb_type->Enable(is_new);
        //txt_src->Enable(is_new);
        txt_style = txt_src->GetWindowStyleFlag();
        if(is_new)
        {
            txt_style &= ~(long)wxTE_READONLY;
        }
        else
        {
            txt_style |= wxTE_READONLY;
        }
        txt_src->SetWindowStyleFlag(txt_style);
        btn_browse_src->Enable(is_new);

        cmb_dst_part->Enable(is_new);
        txt_dst->SetWindowStyleFlag(txt_style);
        cmb_emmc_pos->Enable(is_new);

        txt_style |= wxTE_MULTILINE;
        txt_cmdline->SetWindowStyleFlag(txt_style);
        if(GetUpdateItemType() != SECTION_COMMAND)
        {
            btn_extract->Enable(!is_new);
        }
    }
}

void AvmUpdateItemPanel::OnBrowseSourceFile(wxCommandEvent& event)
{
    if(txt_src == NULL)
    {
        return;
    }
    wxFileDialog dlg(this, _("Select source file ..."), wxFileName(output_bin).GetPath(), wxEmptyString, wxT("All files (*)|*"), wxFD_FILE_MUST_EXIST);
    if(dlg.ShowModal() == wxID_OK)
    {
        txt_src->SetValue(dlg.GetPath());
        wxString filename = wxFileName(dlg.GetPath()).GetFullName();
        wxString old_dst = txt_dst->GetValue();
        if(old_dst.IsEmpty())
        {
            txt_dst->SetValue(wxT("/") + filename);
        }
        else
        {
            txt_dst->SetValue(wxFileName(old_dst).GetPath() + wxT("/") + filename);
        }
    }
}

bool AvmUpdateItemPanel::AppendUpdateItem(const wxString& output_bin)
{
    if(ui_ok)
    {
        switch(cmb_type->GetSelection())
        {
        case SECTION_COMMAND://command
            return AppendCommandItem(output_bin);
        case SECTION_FS_FILE://filesystem file
            return AppendFilesystemItem(output_bin);
        case SECTION_RAW_DATA://raw emmc
            return AppendRawItem(output_bin);
        default:
            return false;
        }
    }
    return false;
}

bool AvmUpdateItemPanel::AppendCommandItem(const wxString& output)
{
    if(is_new == false)
    {
        return true;
    }
    wxString dst = output;
    if(dst.Length() <= 0)
        dst = output_bin;
    if(dst.Length() <= 0)
        return false;
    wxString cmd_line = txt_cmdline->GetValue().Strip();
    if(cmd_line.Length() == 0)
    {
        wxMessageBox(_("Command line should not be empty!"));
        return false;
    }
    //预处理命令行
    PreprocessCmdLine(cmd_line);
    int err = build_section(NULL, "Command line", dst.c_str(), 0L, NULL, cmd_line.c_str());
    if(err == 0)
    {
        is_new = false;
        SetNewFlag(false);
        return true;
    }
    return false;
}

bool AvmUpdateItemPanel::AppendFilesystemItem(const wxString& output)
{
    if(is_new == false)
    {
        return true;
    }
    wxString src_file = txt_src->GetValue().Strip();
    wxString dst_file;
    wxString dst = output;
        if(dst.Length() <= 0)
            dst = output_bin;
        if(dst.Length() <= 0)
            return false;
        dst_file = GetTargetPath();// + txt_dst->GetValue().Strip();
        dst_file.Replace(wxT("${rootfs}"), wxT("/tmp/rootfs"));
        dst_file.Replace(wxT("${opt}"), wxT("/tmp/opt"));
    wxString desc = wxFileName(dst_file).GetName().Left(31);
    if(src_file.Length() > 0)
    {//if the user select a source file for it
        int err = 0;
        if(auto_remove)
        {
            err = build_section(NULL, wxString::Format(wxT("Removing %s"), desc).c_str(),
                output.c_str(), -1, NULL, wxString::Format(wxT("rm -fv %s"), dst_file).c_str(),
                0, 0);
            if(err != 0)
            {
                wxMessageBox(_("remove failed for") + dst_file);
                return false;
            }
        }
        err = build_section(src_file.c_str(), desc.c_str(), output.c_str(),
            -1, dst_file.c_str(), NULL);
        if(err == 0)
        {
            is_new = false;
            return true;
        }
        return false;
    }
    else
    {
        if(source_binary_path.Length() <= 0 || source_size == 0)
        {
            wxMessageBox(_("Source file should be presented!"));
            return false;
        }
        //这种场景是从一个目录切换到另一目录，既然是原来已经存在的文件，则不自动追加删除命令
        int err = build_section(source_binary_path.c_str(), desc.c_str(), output.c_str(),
            -1, dst_file.c_str(), NULL, source_offset, source_size);
        return err == 0;

    }
}

bool AvmUpdateItemPanel::AppendRawItem(const wxString& output)
{
    if(is_new == false)
    {
        return true;
    }
    wxString src_file = txt_src->GetValue().Strip();
    wxString str_offset = cmb_emmc_pos->GetValue();
    if(src_file.Length() == 0)
    {
        wxMessageBox(_("Source file should be present!"));
        return false;
    }
    wxULongLong_t offset = 0;
    if(str_offset.compare(0, 2, "0x") == 0 || str_offset.compare(0, 2, "0X") == 0)
    {
        if(str_offset.ToULongLong(&offset, 16) != true)
        {
            wxMessageBox(_("Offset is not a valid number!"));
            return false;
        }
    }
    else
    {
        if(str_offset.ToULongLong(&offset, 10) != true)
        {
            wxMessageBox(_("Offset is not a valid number!"));
            return false;
        }
    }

    wxString dst = output;
    if(dst.Length() <= 0)
        dst = output_bin;
    if(dst.Length() <= 0)
        return false;
    int err = build_section(src_file.c_str(), "raw partitions", dst.c_str(), offset, NULL, NULL);
    if(err == 0)
    {
        is_new = false;
        return true;
    }
    return false;
}

int AvmUpdateItemPanel::GetUpdateItemType() const
{
    if(ui_ok)
    {
        switch(cmb_type->GetSelection())
        {
        case 0://command
            return SECTION_RAW_DATA;
        case 1://fs
            return SECTION_FS_FILE;
        case 2:
            return SECTION_COMMAND;
        default:
            return -1;
        }
    }
    return -1;
}

wxString AvmUpdateItemPanel::GetTargetPath() const
{
    wxString target = wxEmptyString;
    if(ui_ok)
    {
        wxString path_roots[] = { wxT(""), wxT("${rootfs}"), wxT("${opt}"), wxT("")};
        int type = GetUpdateItemType();
        switch(type)
        {
        case SECTION_FS_FILE:
            target = path_roots[cmb_dst_part->GetSelection()] + txt_dst->GetValue();
            break;
        default:
            break;
        }
    }
    return target;
}

wxString AvmUpdateItemPanel::GetCommandLine() const
{
    wxString cmd = wxEmptyString;
    if(ui_ok)
    {
        if(GetUpdateItemType() == SECTION_COMMAND)
        {
            cmd = txt_cmdline->GetValue();
        }
    }
    return cmd;
}

bool AvmUpdateItemPanel::UpdateInformation(const SectionHeader& item)
{
    /*if(ui_ok)
    {
        switch(item.flag)
        {
        case SECTION_RAW_DATA:
            cmb_emmc_pos->SetString(wxString::Format(wxT("%llu"), item.blk_offset));
            break;
        case SECTION_FS_FILE:
            {
                typedef struct{
                    uint32_t path_len;
                    char path[0];
                }DstFileInfo;
                DstFileInfo* file_info = (DstFileInfo*)
            }

    }*/
    return true;
}

void AvmUpdateItemPanel::SetCommandLine(const wxString& cmd)
{
    if(ui_ok)
    {
        wxString show = cmd;
        PreprocessCmdLine(show, true);
        txt_cmdline->SetValue(show);
    }
}

void AvmUpdateItemPanel::SetTargetPath(const wxString& path)
{
    if(ui_ok)
    {
        int idx = 1;
        MountPointsMap::const_iterator it = AvmUpdateItemPanel::TmpMountPoints.begin();
        MountPointsMap::const_iterator end = AvmUpdateItemPanel::TmpMountPoints.end();
        while(it != end)
        {
            const char* top_path = (*it).second;
            size_t top_path_len = strlen(top_path);
            if(strncmp(top_path, path.c_str(), top_path_len) == 0)
            {
                cmb_dst_part->SetSelection(idx);
                txt_dst->SetValue(path.Right(path.Length() - top_path_len));
                break;
            }
            ++idx;
            ++it;
        }
        if(it == end)
        {
            cmb_dst_part->SetSelection(0);
            txt_dst->SetValue(path);
        }
        return;
    }
}

void AvmUpdateItemPanel::SetEmmcPosition(uint64_t off)
{
    if(ui_ok)
    {
        cmb_emmc_pos->SetValue(wxString::Format(wxT("%llu"), off));
    }
}

void AvmUpdateItemPanel::SetUpdateItemType(int type)
{
    switch(type)
    {
    case SECTION_COMMAND:
        SelectUpdateItemType(SECTION_COMMAND); break;
    case SECTION_FS_FILE:
        SelectUpdateItemType(SECTION_FS_FILE); break;
    case SECTION_RAW_DATA:
        SelectUpdateItemType(SECTION_RAW_DATA); break;
    default:
        break;
    }
}

void AvmUpdateItemPanel::PreprocessCmdLine(wxString& cmdline, bool reverse_convert)
{
    MountPointsMap::const_iterator it = AvmUpdateItemPanel::TmpMountPoints.begin();
    MountPointsMap::const_iterator end = AvmUpdateItemPanel::TmpMountPoints.end();

    while(it != end)
    {
        if(reverse_convert == false)
        {
            cmdline.Replace((*it).first, (*it).second);
        }
        else
        {
            cmdline.Replace((*it).second, (*it).first);
        }
        it++;
    }
}

void AvmUpdateItemPanel::OnBtnExtract(wxCommandEvent& event)
{
    if(wxFileName::Exists(source_binary_path))
    {
        wxString filename = wxFileName(txt_dst->GetValue()).GetFullName();
        wxFileDialog dlg(this, _("Save as..."), wxEmptyString, filename, wxFileSelectorDefaultWildcardStr, wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
        if(dlg.ShowModal() == wxID_OK)
        {
            filename = dlg.GetPath();
            if(SaveDataFromFile(source_binary_path, source_offset, source_size, filename))
            {
                wxMessageBox(_("Save file successfully!"));
            }
        }
    }
    else
    {
        wxMessageBox(_("Source doest not exist!"));
    }
}

bool AvmUpdateItemPanel::SaveDataFromFile(const wxString& src, wxFileOffset offset, off_t length, const wxString& dst)
{
    const size_t step = 1024 * 1024;
    wxFileOffset cur_pos;
    char buffer[step];
    size_t cur_step;

    wxFile fi(src, wxFile::read);
    if(fi.IsOpened())
    {
        wxFile fo(dst, wxFile::write);
        if(!fo.IsOpened())
        {
            wxMessageBox(_("Open destination file for write failed!"));
            return false;
        }
        cur_pos = fi.Seek(offset, wxFromStart);
        if(cur_pos == offset)
        {
            off_t done = 0;
            off_t left = length;
            while(cur_pos < fi.Length() && done < length)
            {
                if(left > (off_t)step)
                {
                    cur_step = step;
                }
                else
                {
                    cur_step = (size_t)left;
                }
                ssize_t got = fi.Read(buffer, cur_step);
                if(got > 0)
                {
                    if(fo.Write(buffer, got) != (size_t)got)
                    {
                        wxMessageBox(_("Write destination failed failed!"));
                        break;
                    }
                    done += got;
                    left -= got;
                }
                else
                {
                    wxMessageBox(_("Read source file failed!"));
                    break;
                }
            }
            if(left == 0)
            {
                return true;
            }
        }
        else
        {
            wxMessageBox(_("Move read position within the source file failed!"));
        }
    }
    else
    {
        wxMessageBox(_("Open source file for read failed!"));
    }
    return false;
}

void AvmUpdateItemPanel::OnBtnDelete(wxCommandEvent& event)
{
    wxCommandEvent evt(AvmUpdateItemPanel::Evt_DeleteItem);
    evt.SetClientData(this);
    wxPostEvent(GetParent(), evt);
}

bool AvmUpdateItemPanel::DumpMakeCommand(const wxString& cmd_path)
{
    wxFileName fn(cmd_path);
    wxString dir = fn.GetPath();
    bool ret = false;
    if(wxFileName::Exists(dir))
    {
        switch(GetUpdateItemType())
        {
            case SECTION_COMMAND:
                ret = DumpCommand(cmd_path);
                break;
            case SECTION_FS_FILE:
                ret = DumpFsfile(cmd_path);
                break;
            case SECTION_RAW_DATA:
                ret = DumpRawdata(cmd_path);
                break;
            default:
                break;
        }
    }
    return ret;
}

bool AvmUpdateItemPanel::DumpCommand(const wxString& cmd_path)
{
    wxString cmd = GetCommandLine();
    wxString content = wxString::Format(wxT("emmctool --build -e avmupdate.bin \"%s\""), cmd);
    return AppendMakeCommand(cmd_path, content);
    return true;
}

bool AvmUpdateItemPanel::DumpFsfile(const wxString& cmd_path)
{
    wxString dst_path = txt_dst->GetValue();
    wxString name = wxFileName(dst_path).GetFullName();
    wxString src_path = wxFileName(cmd_path).GetPath() + wxT("/") + name;
    if(SaveDataFromFile(source_binary_path, source_offset, source_size, src_path))
    {
        const char* mount_point = GetItemMountPoint();
        const wxString fs_path = txt_dst->GetValue();
        wxString dst_path = wxString::Format(wxT("%s%s%s"), mount_point, fs_path[0]==wxT('/')?"":"/", fs_path);
        wxString cmd = wxString::Format(wxT("emmctool --build -f avmupdate.bin  %s %s"), name, dst_path);
        if(AppendMakeCommand(cmd_path, cmd))
            return true;
        wxRemoveFile(src_path);
    }
    return false;
}

bool AvmUpdateItemPanel::DumpRawdata(const wxString& cmd_path)
{
    wxString dir = wxFileName(cmd_path).GetPath();
    uint32_t file_idx = Utils::Get().GetNextRawfileIndex(dir);
    wxString dst = wxString::Format(wxT("%s/%u"), dir, file_idx);
    if(SaveDataFromFile(source_binary_path, source_offset, source_size, dst))
    {
        wxString cmd = wxString::Format(wxT("emmctool --build avmupdate.bin %u %s"), file_idx, cmb_emmc_pos->GetValue());
        if(AppendMakeCommand(cmd_path, cmd))
        {
            return true;
        }
        wxRemoveFile(dst);
    }
    return false;
}

bool AvmUpdateItemPanel::AppendMakeCommand(const wxString& cmd_path, const wxString& cmd)
{
    wxFile file;
    if(file.Open(cmd_path, wxFile::write_append))
    {
        wxString content = cmd;
        Utils::Get().ProcessCmmandTags(content);
        return file.Write(content + wxT("\n"));
    }
    return false;
}

const char* AvmUpdateItemPanel::GetItemMountPoint()
{
    int part = cmb_dst_part->GetSelection();
    const char** mount_points = Utils::Get().GetMountPoints();
    return mount_points[part];
}
